/*
 * WolfGo - the virtual semantic drive.
 *  
 */

package com.logicfishsoftware.wolfgo.filesys;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import net.decasdev.dokan.Dokan;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.util.internal.ExecutorUtil;
import org.openrdf.model.util.GraphUtilException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.config.RepositoryConfigException;
import org.openrdf.repository.manager.RepositoryManager;
import org.openrdf.repository.manager.RepositoryProvider;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.repository.object.config.ObjectRepositoryFactory;
import org.openrdf.result.Result;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;

import com.logicfishsoftware.wolfgo.api.VirtualDirectory;
import com.logicfishsoftware.wolfgo.api.VirtualFile;
import com.logicfishsoftware.wolfgo.api.VirtualFileSystem;
import com.logicfishsoftware.wolfgo.api.VirtualFileSystemConnection;
import com.logicfishsoftware.wolfgo.api.VirtualFileSystemException;
import com.logicfishsoftware.wolfgo.host.RestfulServerPipelineFactory;
import com.logicfishsoftware.wolfgo.host.fs.RestfulFileSystem;
import com.logicfishsoftware.wolfgo.meta.SemanticFileSystemProvider;
import com.logicfishsoftware.wolfgo.meta.SemanticFileSystemProvider.SemanticFileSystemHandler;
import com.logicfishsoftware.wolfgo.meta.SesameUtils;
import com.logicfishsoftware.wolfgo.mount.dokan.VirtualMount_Dokan;

/**
 * WolfGo - main class.
 * @author Home
 *
 */
public class WolfGo implements WolfConstants {

	private static final String DRIVE_LETTER = "J";
	private static final int WEB_PORT = 9900;
	private static final String REPOSITORY_ID = "wolfgo";

	private static final Options options = new Options();
	static {
		Option u = new Option("u", "unmount", false, "Unmount filesystem.");
		Option letter = new Option("d", "drive", true,
				"set the mount-point drive letter");
		letter.setArgName("drive letter");
		letter.setOptionalArg(true);

		Option p = new Option("p", "port", true,
				"set the listen port for the webserver");
		p.setArgName("web port");
		p.setOptionalArg(true);

		Option host = new Option("a", "address", true,
				"set the listen address for the webserver");
		host.setArgName("web host");
		host.setOptionalArg(true);

		Option template = new Option("t", "template", true,
				"create new repository using template (wolfgo-memory or wolfgo-native)");
		template.setArgName("template");
		template.setOptionalArg(true);

		Option r = new Option("r", "repository", true,
				"connect to named repository ID");
		r.setArgName("repository-id");
		r.setOptionalArg(true);

		Option c = new Option("c", "compile", false, "Enable the ontology compiler");
		c.setOptionalArg(true);

		Option z = new Option("z", "remote", true, "Connect to remote system [hostname:port]");
		z.setArgName("remote-connection");
		z.setOptionalArg(true);
		
		Option threadCount = new Option("p", "thread-count", true,
				"set number of threads for dokan mount system");
		threadCount.setArgName("threadCount");
		threadCount.setOptionalArg(true);
		
		options.addOption("h", "help", false, "Print Help (this message) and exit");
		options.addOption("v", "version", false, "Print version information and exit");
		options.addOption(template);
		options.addOption(u);
		options.addOption(p);
		options.addOption(host);
		options.addOption(c);
		options.addOption(letter);
		options.addOption(threadCount);
		options.addOption(r);
		options.addOption(z);
		
	}
	public static void main(String[] args) {
		String driveLetter = DRIVE_LETTER;
		int port = WEB_PORT;
		boolean compile = false;
		boolean useTemplate = false;
		boolean useRemote = false;
		String template = null;
		String repositoryId = REPOSITORY_ID;
		InetAddress bindAddress = InetAddress.getLoopbackAddress();
		String remoteHost;
		int remotePort;
		int threadCount = 10;
		
		try {
			CommandLine line = new GnuParser().parse(options, args);
			if (line.hasOption('h')) {
				HelpFormatter formatter = new HelpFormatter();
				String cmdLineSyntax = WolfGo.class.getSimpleName()
						+ " [options]";
				String header = "WolfGo Virtual Semantic Drive.";
				formatter.printHelp(cmdLineSyntax, header, options, "");
				return;
			}
			if (line.hasOption('v')) {
				System.out.println(APP_NAME + " " + VERSION);
				int version = Dokan.getVersion();
				System.out.println("Dokan version = " + version);
				int driverVersion = Dokan.getDriverVersion();
				System.out.println("Dokan driver version = " + driverVersion);
				return;
			}
			if(line.hasOption('d')) {
				driveLetter = line.getOptionValue('d').substring(0,1);
			}
			if(line.hasOption('p')) {
				port = Integer.parseInt(line.getOptionValue('p'));
			}
			if(line.hasOption('a')) {
				bindAddress = InetAddress.getByName(line.getOptionValue('a'));
			}
			if(line.hasOption('u')) {
				Dokan.removeMountPoint(driveLetter);
				System.exit(0);
			}
			if(line.hasOption('c')) {
				compile = true;
			}
			if(line.hasOption('t')) {
				useTemplate = true;
				template = line.getOptionValue('t');
			}
			if(line.hasOption('r')) {
				repositoryId = line.getOptionValue('r');
			}
			if(line.hasOption('p')) {
				threadCount = Integer.parseInt(line.getOptionValue('p'));
			}
			if(line.hasOption('z')) {
				useRemote = true;
				String lineString = line.getOptionValue('z');
				String[] parts = lineString.split(":");
				if(parts.length!=2) 
					throw new IllegalArgumentException("Remote connection needs HOSTNAME:PORT");
				remoteHost = parts[0];
				remotePort = Integer.parseInt(parts[1]);
			}
		} catch (ParseException | UnknownHostException exp) {
			System.err.println(exp.getMessage());
			System.exit(1);
		}


		// Create repos:

		ObjectRepository objectRepository = null;

		try {
			if(!useRemote) {
					if(useTemplate) {
						objectRepository = SesameUtils.createRepos(repositoryId,template);
					} else {
						objectRepository = SesameUtils.connectRepos(repositoryId);
					}
					objectRepository.setCompileRepository(compile);
			}
		} catch (RepositoryConfigException | RepositoryException | RDFParseException | RDFHandlerException | GraphUtilException | IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			System.exit(-1);
		}

		// Configure the server.

		final Executor boss = Executors.newCachedThreadPool();
		final Executor worker = Executors.newCachedThreadPool();

		ServerBootstrap bootstrap = new ServerBootstrap(
				new NioServerSocketChannelFactory(
						boss,
						worker));

		// Configure the meta-data service
		VirtualFileSystem virtualFileSys = null;

		final ObjectRepository repository = objectRepository;
		final SemanticFileSystemHandler handler = new SemanticFileSystemProvider.SemanticFileSystemHandler() {
			@Override
			public void shutdown(VirtualFileSystem sys) {
				try {
					if(repository!=null) {
						repository.shutDown();
					}
				} catch (RepositoryException e) {
					e.printStackTrace(System.err);
				} finally {
					ExecutorUtil.terminate(worker,boss);
					System.out.print("Shutdown.");					
				}
			}

			@Override
			public ObjectConnection getConnection(
					VirtualFileSystem fileSystem)
					throws RepositoryException {
				return repository.getConnection();
			}

			@Override
			public void disposeConnection(ObjectConnection connection) throws RepositoryException {
				connection.commit();
//				try {
//					Result<VirtualDirectory> result = connection.getObjects(VirtualDirectory.class);
//					while (result.hasNext()) {
//						  System.out.println("Dir: " + result.next().getFileName());
//					}
//				} catch (QueryEvaluationException | ClassCastException e) {
//					e.printStackTrace();
//				} finally {
//					try {
//						Result<VirtualFile> result = connection.getObjects(VirtualFile.class);
//						while (result.hasNext()) {
//						  System.out.println("File: " + result.next().getFileName());
//						}
//					} catch (QueryEvaluationException | ClassCastException e) {
//						e.printStackTrace();
//					} finally {
//						connection.close();
//					}
//				}
				connection.close();
			}

			@Override
			public void rollback(VirtualFileSystemException e,
					ObjectConnection connection) {
				System.err.println("Rolling back on error.");
				e.printStackTrace(System.err);
				try {
					connection.rollback();
				} catch (RepositoryException e1) {
					e1.printStackTrace(System.err);
				}
				
			}

		};
		if(useRemote) {
			virtualFileSys = new RestfulFileSystem();			
		} else {
			try {
				virtualFileSys = SemanticFileSystemProvider.createSemanticFS(handler);
			} catch (VirtualFileSystemException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
				System.exit(-1);
			}
		}
		
		VirtualFileSystemConnection connection = null;
		try {
			connection = virtualFileSys.connect();
			connection.put(File.separator, VirtualDirectory.class);
		} catch (Exception e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			System.exit(-1);
		} finally {
			if(connection!=null) connection.close();
		}
		// Set up the event pipeline factory.
		bootstrap.setPipelineFactory(new RestfulServerPipelineFactory(driveLetter + ":\\",virtualFileSys));

		// Start the file system.
		final String _driveLetter = driveLetter;
		final VirtualFileSystem filesys = virtualFileSys;
		final int _threadCount = threadCount;
		new Thread(new Runnable(){
			@Override
			public void run() {
				try { 
					VirtualMount_Dokan.start(_driveLetter,filesys,_threadCount);
				} finally {
					System.out.print("Closing...");
					filesys.shutdown();					
				}				
			}}).start();

		// Bind and start web-server to accept incoming connections.
		bootstrap.bind(new InetSocketAddress(bindAddress,port));		

	}
}
